﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace SilverlightControls.Designer
{
	#region Using Directives

    

    #endregion

	#region PropertyGrid
	/// <summary>
	/// PropertyGrid
	/// </summary>
	public class PropertyGrid : ContentControl
	{
		#region Fields

		//#99B4D1
		//internal static Color backgroundColor = Color.FromArgb(127, 153, 180, 209);

		//#E9ECFA
		internal static Color BackgroundColor = Color.FromArgb(255, 233, 236, 250);
		internal static Color BackgroundColorFocused = Color.FromArgb(255, 94, 170, 255);

		static readonly Type _thisType = typeof(PropertyGrid);

		ValueEditorBase _selectedEditor;
		ScrollViewer _layoutRoot;
		Grid _mainGrid;
		bool _loaded = false;
		bool _resetLoadedObject;

		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		public PropertyGrid()
        {
            Resources.Source = new Uri("/SilverlightControls;component/Designer/Themes/Generic.xaml", UriKind.Relative);
			base.DefaultStyleKey = typeof(PropertyGrid);
			this.Loaded += PropertyGrid_Loaded;
		}
		#endregion

		#region Properties

		#region SelectedObject

		public static readonly DependencyProperty SelectedObjectProperty =
		  DependencyProperty.Register("SelectedObject", typeof(object), _thisType, new PropertyMetadata(null, OnSelectedObjectChanged));

		public object SelectedObject
		{
			get { return GetValue(SelectedObjectProperty); }
			set { SetValue(SelectedObjectProperty, value); }
		}

		private static void OnSelectedObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var propertyGrid = d as PropertyGrid;
			if (propertyGrid != null)
			{
				if (!propertyGrid._loaded)
					propertyGrid._resetLoadedObject = true;
				else if (null != e.NewValue)
					propertyGrid.ResetObject(e.NewValue);
				else
					propertyGrid.ResetMainGrid();
			}
		}
		#endregion

		#region Default LabelWidth
		/// <summary>
		/// The DefaultLabelWidth DependencyProperty
		/// </summary>
		public static readonly DependencyProperty DefaultLabelWidthProperty =
		  DependencyProperty.Register("DefaultLabelWidth", typeof(int), _thisType, new PropertyMetadata(75));
		/// <summary>
		/// Gets or sets the Default Width for the labels
		/// </summary>
		public int DefaultLabelWidth
		{
			get { return (int)GetValue(DefaultLabelWidthProperty); }
			set { SetValue(DefaultLabelWidthProperty, value); }
		}
		#endregion

		#region Grid BorderBrush

		public static readonly DependencyProperty GridBorderBrushProperty =
			DependencyProperty.Register("GridBorderBrush", typeof(Brush), _thisType, new PropertyMetadata(new SolidColorBrush(Colors.LightGray), OnGridBorderBrushChanged));

		/// <summary>
		/// Gets or sets the Border Brush of the Property Grid
		/// </summary>
		public Brush GridBorderBrush
		{
			get { return (Brush)GetValue(GridBorderBrushProperty); }
			set { SetValue(GridBorderBrushProperty, value); }
		}

		private static void OnGridBorderBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var propertyGrid = d as PropertyGrid;
			if (propertyGrid != null && null != propertyGrid._layoutRoot && null != e.NewValue)
				propertyGrid._layoutRoot.BorderBrush = (SolidColorBrush)e.NewValue;
		}

		#endregion

		#region Grid BorderThickness

		public static readonly DependencyProperty GridBorderThicknessProperty =
			DependencyProperty.Register("GridBorderThickness", typeof(Thickness), _thisType, new PropertyMetadata(new Thickness(1), OnGridBorderThicknessChanged));

		/// <summary>
		/// Gets or sets the Border Thickness of the Property Grid
		/// </summary>
		public Thickness GridBorderThickness
		{
			get { return (Thickness)base.GetValue(GridBorderThicknessProperty); }
			set { base.SetValue(GridBorderThicknessProperty, value); }
		}


		private static void OnGridBorderThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var propertyGrid = d as PropertyGrid;
			if (propertyGrid != null && null != propertyGrid._layoutRoot && null != e.NewValue)
				propertyGrid._layoutRoot.BorderThickness = (Thickness)e.NewValue;
		}

		#endregion

		#endregion

		#region Overrides
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			_layoutRoot = (ScrollViewer)GetTemplateChild("LayoutRoot");
			_mainGrid = (Grid)GetTemplateChild("MainGrid");

			_loaded = true;

			if (_resetLoadedObject)
			{
				_resetLoadedObject = false;
				ResetObject(SelectedObject);
			}
		}
		#endregion

		#region Methods

		int SetObject(object obj)
		{
			var props = new List<PropertyItem>();
			int rowCount = -1;

			// Parse the objects properties
			props = ParseObject(obj);

			#region Render the Grid

			var categories = (from p in props
							  orderby p.Category
							  select p.Category).Distinct();

			foreach (string category in categories)
			{

				this.AddHeaderRow(category, ref rowCount);

				var items = from p in props
							where p.Category == category
							orderby p.Name
							select p;

				foreach (var item in items)
					this.AddPropertyRow(item, ref rowCount);

			}
			#endregion

			return rowCount++;

		}

		void ResetObject(object obj)
		{
			this.ResetMainGrid();

			int rowCount = this.SetObject(obj);

			if (rowCount > 0)
				AddGridSplitter(rowCount);
		}
		void ResetMainGrid()
		{
			this._mainGrid.Children.Clear();
			this._mainGrid.RowDefinitions.Clear();
		}
		void AddHeaderRow(string category, ref int rowIndex)
		{
			rowIndex++;
			_mainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(21) });

			#region Column 0 - Margin

			Border brd = GetCategoryMargin(category, GetHideImage(Visibility.Visible), GetShowImage(Visibility.Collapsed));
			_mainGrid.Children.Add(brd);
			Grid.SetRow(brd, rowIndex);
			Grid.SetColumn(brd, 0);

			#endregion

			#region Column 1 & 2 - Category Header

			brd = GetCategoryHeader(category);
			_mainGrid.Children.Add(brd);
			Grid.SetRow(brd, rowIndex);
			Grid.SetColumn(brd, 1);
			Grid.SetColumnSpan(brd, 2);

			#endregion
		}
		void AddPropertyRow(PropertyItem item, ref int rowIndex)
		{
			#region Create Display Objects
			PropertyGridLabel label = CreateLabel(item.Name, item.DisplayName);
			ValueEditorBase editor = EditorService.GetEditor(item, label);
			if (null == editor)
				return;
			editor.GotFocus += new RoutedEventHandler(this.Editor_GotFocus);
			#endregion

			rowIndex++;
			_mainGrid.RowDefinitions.Add(new RowDefinition());
			string tagValue = item.Category;

			#region Column 0 - Margin
			Border brd = GetItemMargin(tagValue);
			_mainGrid.Children.Add(brd);
			Grid.SetRow(brd, rowIndex);
			Grid.SetColumn(brd, 0);
			#endregion

			#region Column 1 - Label
			brd = GetItemLabel(label, tagValue);
			_mainGrid.Children.Add(brd);
			Grid.SetRow(brd, rowIndex);
			Grid.SetColumn(brd, 1);
			#endregion

			#region Column 2 - Editor
			brd = GetItemEditor(editor, tagValue);
			_mainGrid.Children.Add(brd);
			Grid.SetRow(brd, rowIndex);
			Grid.SetColumn(brd, 2);
			#endregion
		}
		void AddGridSplitter(int rowCount)
		{
			var gsp = new GridSplitter
			              {
				IsTabStop = false,
				HorizontalAlignment = HorizontalAlignment.Left,
				VerticalAlignment = VerticalAlignment.Stretch,
				Background = new SolidColorBrush(Colors.Transparent),
				ShowsPreview = false,
				Width = 2
			};
			Grid.SetColumn(gsp, 2);
			Grid.SetRowSpan(gsp, rowCount);
			Canvas.SetZIndex(gsp, 1);
			_mainGrid.Children.Add(gsp);

		}
		void ToggleCategoryVisible(bool show, string tagValue)
		{
			foreach (FrameworkElement element in _mainGrid.Children)
			{
				object value = element.Tag;
				if (null != value)
				{
					var tag = (string)value;
					if (tagValue == tag)
						element.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
				}
			}
		}
		void AttachWheelEvents()
		{
		    try
            {
                HtmlPage.Window.AttachEvent("DOMMouseScroll", OnMouseWheel);
                HtmlPage.Window.AttachEvent("onmousewheel", OnMouseWheel);
                HtmlPage.Document.AttachEvent("onmousewheel", OnMouseWheel);
		    }
		    catch{}
		}
		void DetachWheelEvents()
		{
            try
            {
                HtmlPage.Window.DetachEvent("DOMMouseScroll", OnMouseWheel);
                HtmlPage.Window.DetachEvent("onmousewheel", OnMouseWheel);
                HtmlPage.Document.DetachEvent("onmousewheel", OnMouseWheel);
            }
            catch { }
		}
		Image GetHideImage(Visibility visibility)
		{
            Image img = GetImage("/SilverlightControls;Designer/Assets/minus.png");
			img.Visibility = visibility;
			img.MouseLeftButtonUp += CategoryHide_MouseLeftButtonUp;
			return img;
		}
		Image GetShowImage(Visibility visibility)
		{
            Image img = GetImage("/SilverlightControls;Designer/Assets/plus.png");
			img.Visibility = visibility;
			img.MouseLeftButtonUp += CategoryShow_MouseLeftButtonUp;
			return img;
		}

		static List<PropertyItem> ParseObject(object objItem)
		{
			if (null == objItem)
				return new List<PropertyItem>();

			var pc = new List<PropertyItem>();
			Type t = objItem.GetType();
			var props = t.GetProperties();

			foreach (PropertyInfo pinfo in props)
			{
				bool isBrowsable = true;
				var b = PropertyItem.GetAttribute<BrowsableAttribute>(pinfo);
				if (null != b)
					isBrowsable = b.Browsable;
				if (isBrowsable)
				{
					var eb = PropertyItem.GetAttribute<EditorBrowsableAttribute>(pinfo);
					if (null != eb && eb.State == EditorBrowsableState.Never)
						isBrowsable = false;
				}
				if (isBrowsable)
				{
					bool readOnly = false;
					var attr = PropertyItem.GetAttribute<ReadOnlyAttribute>(pinfo);
					if (attr != null)
						readOnly = attr.IsReadOnly;

					try
					{
						object value = pinfo.GetValue(objItem, null);
						var prop = new PropertyItem(objItem, value, pinfo, readOnly);
						pc.Add(prop);
					}
					catch { }
				}
			}

			return pc;
		}
		static PropertyGridLabel CreateLabel(string name, string displayName)
		{
			var txt = new TextBlock
			              {
				Text = displayName,
				Margin = new Thickness(0)
			};
			return new PropertyGridLabel
			           {
				Name = Guid.NewGuid().ToString("N"),
				Content = txt
			};
		}
		static Border GetCategoryMargin(string tagValue, Image hide, Image show)
		{
			var stp = new StackPanel
			              {
			                  Name = Guid.NewGuid().ToString("N"),
			                  HorizontalAlignment = HorizontalAlignment.Center,
			                  VerticalAlignment = VerticalAlignment.Center,
			                  Tag = tagValue
			              };
		    stp.Children.Add(hide);
			stp.Children.Add(show);

			var brd = new Border {Background = new SolidColorBrush(BackgroundColor), Child = stp};

		    return brd;
		}
		static Border GetCategoryHeader(string category)
		{
		    var txt = new TextBlock
		                  {
		                      Name = Guid.NewGuid().ToString("N"),
		                      Text = category,
		                      VerticalAlignment = VerticalAlignment.Center,
		                      HorizontalAlignment = HorizontalAlignment.Left,
		                      Foreground = new SolidColorBrush(Colors.Gray),
		                      Margin = new Thickness(3, 0, 0, 0),
		                      FontWeight = FontWeights.Bold,
		                      FontFamily = new FontFamily("Arial Narrow")
		                  };

			var brd = new Border
			              {
			                  Background = new SolidColorBrush(BackgroundColor),
			                  Child = txt
			              };
		    Canvas.SetZIndex(brd, 1);
			return brd;
		}
		static Border GetItemMargin(string tagValue)
		{
		    return new Border
		               {
		                   Name = Guid.NewGuid().ToString("N"),
		                   Margin = new Thickness(0),
		                   BorderThickness = new Thickness(0),
		                   Background = new SolidColorBrush(BackgroundColor),
		                   Tag = tagValue
		               };
		}
		static Border GetItemLabel(PropertyGridLabel label, string tagValue)
		{
		    return new Border
		               {
		                   Name = Guid.NewGuid().ToString("N"),
		                   Margin = new Thickness(0),
		                   BorderBrush = new SolidColorBrush(BackgroundColor),
		                   BorderThickness = new Thickness(0, 0, 1, 1),
		                   Child = label,
		                   Tag = tagValue
		               };
		}
		static Border GetItemEditor(ValueEditorBase editor, string tagValue)
		{
			var brd = new Border
			              {
			                  Name = Guid.NewGuid().ToString("N"),
			                  Margin = new Thickness(1, 0, 0, 0),
			                  BorderThickness = new Thickness(0, 0, 0, 1),
			                  BorderBrush = new SolidColorBrush(BackgroundColor),
			                  Child = editor,
			                  Tag = tagValue
			              };
		    return brd;
		}
		static Image GetImage(string imageUri)
		{
			//
		    var img = new Image
		                  {
		                      Name = Guid.NewGuid().ToString("N"),
		                      Source = new BitmapImage(new Uri(imageUri, UriKind.Relative)),
		                      Height = 9,
		                      Width = 9,
		                      VerticalAlignment = VerticalAlignment.Center,
		                      HorizontalAlignment = HorizontalAlignment.Center
		                  };
			return img;
		}

		#endregion

		#region Event Handlers
		private void PropertyGrid_Loaded(object sender, RoutedEventArgs e)
        {
			MouseEnter += PropertyGrid_MouseEnter;
			MouseLeave += PropertyGrid_MouseLeave;
		}
		private void Editor_GotFocus(object sender, RoutedEventArgs e)
		{
			if (null != _selectedEditor)
				_selectedEditor.IsSelected = false;
			_selectedEditor = sender as ValueEditorBase;
			if (null != _selectedEditor)
			{
				_selectedEditor.IsSelected = true;
			}
		}
		private void PropertyGrid_MouseEnter(object sender, MouseEventArgs e)
		{
			AttachWheelEvents();
		}
		private void PropertyGrid_MouseLeave(object sender, MouseEventArgs e)
		{
			DetachWheelEvents();
		}
		private void OnMouseWheel(object sender, HtmlEventArgs args)
		{
			double mouseDelta = 0;
			ScriptObject e = args.EventObject;

			// Mozilla and Safari    
			if (e.GetProperty("detail") != null)
			{
				mouseDelta = ((double)e.GetProperty("detail"));
			}

				// IE and Opera    
			else if (e.GetProperty("wheelDelta") != null)
				mouseDelta = ((double)e.GetProperty("wheelDelta"));

			mouseDelta = Math.Sign(mouseDelta);
			mouseDelta = mouseDelta * -1;
			mouseDelta = mouseDelta * 40; // Just a guess at an acceleration
			mouseDelta = _layoutRoot.VerticalOffset + mouseDelta;
			_layoutRoot.ScrollToVerticalOffset(mouseDelta);
		}
		private void CategoryHide_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			var ctl = sender as FrameworkElement;
			var stp = ctl.Parent as Panel;
			var tagValue = (string)stp.Tag;
			stp.Children[0].Visibility = Visibility.Collapsed;
			stp.Children[1].Visibility = Visibility.Visible;
			Dispatcher.BeginInvoke(() => ToggleCategoryVisible(false, tagValue));
		}
		private void CategoryShow_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			var ctl = sender as FrameworkElement;
			var stp = ctl.Parent as Panel;
			var tagValue = (string)stp.Tag;
			stp.Children[0].Visibility = Visibility.Visible;
			stp.Children[1].Visibility = Visibility.Collapsed;
			Dispatcher.BeginInvoke(() => ToggleCategoryVisible(true, tagValue));
		}
		#endregion
	}
	#endregion
}
